package info.kimjihyok.ripplelibrary;

import com.czt.mp3recorder.MP3Recorder;
import info.kimjihyok.ripplelibrary.listener.RecordingListener;
import info.kimjihyok.ripplelibrary.renderer.Renderer;
import info.kimjihyok.ripplelibrary.renderer.TimerCircleRippleRenderer;
import info.kimjihyok.ripplelibrary.util.Constant;
import info.kimjihyok.ripplelibrary.util.Rate;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.common.Source;
import ohos.media.player.Player;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;

public class VoiceRippleView extends Component implements Component.DrawTask,
        TimerCircleRippleRenderer.TimerRendererListener {
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD000F00, VoiceRippleView.class.getSimpleName());
    private static final double AMPLITUDE_REFERENCE = 32767.0;
    private static int minRadius;
    private static int minFirstRippleRadius;
    private static final int INVALID_PARAMETER = -1;
    private static final int EVENT_MESSAGE_NORMAL = 1;
    /**
     * 播放
     */
    private Player mPlayer;
    private File file;
    private int buttonRadius;
    private int rippleRadius;
    private int backgroundRadius;
    private boolean isRecording;
    private boolean isPrepared;

    private int rippleDecayRate = INVALID_PARAMETER;
    private int thresholdRate = INVALID_PARAMETER;
    private double backgroundRippleRatio = INVALID_PARAMETER;

    private RecordingListener recordingListener;
    private Renderer curRenderer;
    private int currentRecordedTime = 0;
    private UpdateHandler updateHandler;
    private MP3Recorder mRecorder;
    private int minFirstRadius;

    /**
     * 构造函数
     *
     * @param context context
     * @param attrs   attrs
     */
    public VoiceRippleView(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    /**
     * 绘制刷新类
     *
     * @param currentRenderer currentRenderer
     */
    public void setRenderer(Renderer currentRenderer) {
        this.curRenderer = currentRenderer;
        if (currentRenderer instanceof TimerCircleRippleRenderer) {
            ((TimerCircleRippleRenderer) currentRenderer).setTimerRendererListener(this);
        }
        invalidate();
    }

    private void init(Context con, AttrSet attrs) {
        HiLog.info(LABEL_LOG, attrs.toString());
        minRadius = Constant.NUMBERF165;
        minFirstRippleRadius = Constant.NUMBER9;
        rippleRadius = Constant.NUMBERF165;

        backgroundRadius = rippleRadius;
        buttonRadius = backgroundRadius;
        minFirstRadius = minFirstRippleRadius;
        EventRunner runner = EventRunner.current();
        updateHandler = new UpdateHandler(runner);

        setBackgroundRippleRatio(Constant.NUMBER11F);
        setRippleDecayRate(Rate.MEDIUM);
        setRippleSampleRate(Rate.LOW);

        invalidate();
        addDrawTask(this);
        vodioPlayer(con);
    }

    /**
     * 音频播放
     *
     * @param cont cont
     */
    public void vodioPlayer(Context cont) {
        file = new File(cont.getFilesDir(), "record.mp3");
        try {
            boolean isFileSave = file.createNewFile();
            HiLog.info(LABEL_LOG, "" + isFileSave);
        } catch (IOException e) {
            HiLog.info(LABEL_LOG, "");
        }
        mRecorder = new MP3Recorder(file);
        mPlayer = new Player(cont);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        int viewWidthHalf = component.getWidth() / Constant.NUMBER2;
        int viewHeightHalf = component.getHeight() / Constant.NUMBER2;
        curRenderer.render(canvas, viewWidthHalf, viewHeightHalf, buttonRadius, rippleRadius, backgroundRadius);
    }

    public boolean isRecording() {
        return isRecording;
    }

    /**
     * 设置颜色
     *
     * @param color color
     */
    public void setRippleColor(int color) {
        curRenderer.changeColor(color);
        invalidate();
    }

    /**
     * 采样率
     *
     * @param rate rate
     */
    public void setRippleSampleRate(Rate rate) {
        switch (rate) {
            case LOW:
                this.thresholdRate = Constant.NUMBER5;
                break;
            case MEDIUM:
                this.thresholdRate = Constant.NUMBER10;
                break;
            case HIGH:
                this.thresholdRate = Constant.NUMBER20;
                break;
            default:
        }
        invalidate();
    }

    /**
     * 衰减率
     *
     * @param rate rate
     */
    public void setRippleDecayRate(Rate rate) {
        switch (rate) {
            case LOW:
                this.rippleDecayRate = Constant.NUMBER20;
                break;
            case MEDIUM:
                this.rippleDecayRate = Constant.NUMBER10;
                break;
            case HIGH:
                this.rippleDecayRate = 0;
                break;
            default:
        }
        invalidate();
    }

    /**
     * 设置背景波纹化
     *
     * @param ratio ratio
     */
    public void setBackgroundRippleRatio(double ratio) {
        this.backgroundRippleRatio = ratio;
        minFirstRadius = (int) (minFirstRippleRadius + (minFirstRippleRadius * backgroundRippleRatio));
        invalidate();
    }

    /**
     * 波率
     *
     * @param amplitude amplitude
     */
    public void drop(int amplitude) {
        int powerDb = (int) (Constant.NUMBER20F * Math.log10((double) amplitude / AMPLITUDE_REFERENCE));

        // clip if change is below threshold
        final int threshord = (-1 * powerDb) / thresholdRate;
        if (threshord >= 0) {
            if (rippleRadius - threshord >= powerDb + minRadius + minFirstRadius || powerDb
                    + minRadius + minFirstRadius >= rippleRadius + threshord) {
                rippleRadius = powerDb + minRadius + minFirstRadius;
                backgroundRadius = (int) (rippleRadius * backgroundRippleRatio);
            } else {
                // if decreasing velocity reached 0, it should simply match with ripple radius
                if (rippleDecayRate != 0) {
                    if (((backgroundRadius - rippleRadius) / rippleDecayRate) == 0) {
                        backgroundRadius = rippleRadius;
                        rippleRadius = buttonRadius;
                    } else {
                        backgroundRadius = backgroundRadius - ((backgroundRadius - rippleRadius) / rippleDecayRate);
                        rippleRadius = rippleRadius - ((rippleRadius - buttonRadius) / rippleDecayRate);
                    }
                }
            }
            invalidate();
        }
    }

    @Override
    public void stopRecording() {
        isRecording = false;
        if (isPrepared) {
            mRecorder.stop();
            isPrepared = false;
            int eventid = 0;
            updateHandler.removeEvent(eventid);
            currentRecordedTime = 0;
            invalidate();
            if (recordingListener != null) {
                recordingListener.onRecordingStopped();
            }
        }
    }

    @Override
    public void startRecording() {
        checkValidState();
        try {
            mRecorder.start();
        } catch (IOException e) {
            HiLog.info(LABEL_LOG, "");
        }
        isRecording = true;
        isPrepared = true;
        InnerEvent normalInnerEvent = InnerEvent.get(EVENT_MESSAGE_NORMAL, 1, null);
        updateHandler.sendEvent(normalInnerEvent, 0, EventHandler.Priority.IMMEDIATE);
        invalidate();
        if (recordingListener != null) {
            recordingListener.onRecordingStarted();
        }
    }

    private void checkValidState() {
        if (thresholdRate == INVALID_PARAMETER || backgroundRippleRatio == INVALID_PARAMETER
                || rippleDecayRate == INVALID_PARAMETER) {
            throw new IllegalStateException("Set rippleSampleRate, backgroundRippleRatio and "
                    + "rippleDecayRate before starting to record!");
        }
    }

    /**
     * UpdateHandler
     *
     * @author VoiceRipple
     * @since 2021-04-30
     */
    private class UpdateHandler extends EventHandler {
        private UpdateHandler(EventRunner runner) {
            super(runner);
        }

        // 重写实现processEvent方法
        @Override
        public void processEvent(InnerEvent event) {
            super.processEvent(event);
            if (event == null) {
                return;
            }
            int eventId = event.eventId;

            if (eventId == EVENT_MESSAGE_NORMAL) {
                if (isRecording) {
                    drop(mRecorder.getRealVolume());
                    currentRecordedTime = currentRecordedTime + Constant.NUMBER50;
                    if (curRenderer instanceof TimerCircleRippleRenderer) {
                        ((TimerCircleRippleRenderer) curRenderer).setCurrentTimeMilliseconds(currentRecordedTime);
                    }
                    updateHandler.sendEvent(eventId, Constant.NUMBER50);
                }
            }
        }
    }

    public void setRecordingListener(RecordingListener recordingListener) {
        this.recordingListener = recordingListener;
    }

    /**
     * 播放重置
     */
    public void playReset() {
        mPlayer.reset();
    }

    /**
     * 播放
     *
     * @param fileDes fileDes
     */
    public void startPlayVideo(FileDescriptor fileDes) {
        mPlayer.setSource(new Source(fileDes));
        mPlayer.prepare();
        mPlayer.setPlayerCallback(new IpPlayerCallback());
        mPlayer.play();
    }

    /**
     * 播放音频
     */
    public void filePlay() {
        if (file.exists()) {
            FileInputStream inputStream = null;
            try {
                inputStream = new FileInputStream(file.getCanonicalFile());
                startPlayVideo(inputStream.getFD());
            } catch (IOException e) {
                HiLog.info(LABEL_LOG, "error" + e.toString());
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        HiLog.info(LABEL_LOG, "error" + e.toString());
                    }
                }
            }
        }
    }

    /**
     * IpPlayerCallback
     *
     * @author VoiceRipple
     * @since 2021-04-29
     */
    public static class IpPlayerCallback implements Player.IPlayerCallback {
        @Override
        public void onPrepared() {
        }

        @Override
        public void onMessage(int i, int i1) {
        }

        @Override
        public void onError(int i, int i1) {
        }

        @Override
        public void onResolutionChanged(int i, int i1) {
        }

        @Override
        public void onPlayBackComplete() {
        }

        @Override
        public void onRewindToComplete() {
        }

        @Override
        public void onBufferingChange(int i) {
        }

        @Override
        public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {
        }

        @Override
        public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {
        }
    }
}